#include "loader.h"
#include "comm/elf.h"

/*
 * LBA48模式读取硬盘
 * sector: 起始扇区号（使用64位整数类型容纳48位扇区号）
 * sector_count: 读取扇区数
 */
static void read_disk(uint64_t sector, uint16_t sector_count, void *buf)
{
    outb((uint16_t)0x1f6, (uint8_t)0xe0);  //LBA=1, 驱动器号=0（主硬盘）

    outb((uint16_t)0x1f2, (uint8_t)(sector_count >> 8));    // 扇区数高8位先写入0x1f2端口
    outb((uint16_t)0x1f3, (uint8_t)(sector >> 24));         // 扇区号第4个字节
    outb((uint16_t)0x1f4, (uint8_t)(sector >> 32));         // 扇区号第5个字节
    outb((uint16_t)0x1f5, (uint8_t)(sector >> 40));         // 扇区号第6个字节

    outb((uint16_t)0x1f2, (uint8_t)sector_count);           // 扇区数低8位
    outb((uint16_t)0x1f3, (uint8_t)sector);                 // 扇区号第1个字节
    outb((uint16_t)0x1f4, (uint8_t)(sector >> 8));          // 扇区号第2个字节
    outb((uint16_t)0x1f5, (uint8_t)(sector >> 16));         // 扇区号第3个字节

    outb((uint16_t)0x1f7, (uint8_t)0x24);                   // 发送"READ SECTORS EXT"指令

    uint16_t *data_buf = (uint16_t *)buf;
    while (sector_count--) {
        // 每次读扇区之前，等待就绪状态
        while((inb(0x1f7) & 0x88) != 0x08);

        // 按字读取到数据缓冲区中
        for (int i = 0; i < SECTOR_SIZE / 2; i++)
            *data_buf++ = inw((uint16_t)0x1f0);
    }
}

static uint32_t reload_elf_file(uint8_t *file_buffer)
{
    // 校验ELF文件头
    uint8_t *sig = "\x7F""ELF";
    uint8_t *p = file_buffer;
    while (*sig != '\0') {
        if (*sig++ != *p++)
            return 0;
    }

    Elf32_Ehdr *p_ehdr = (Elf32_Ehdr *)file_buffer;
    for (int i = 0; i < p_ehdr->e_phnum; i++) {
        // 得到每个程序头表地址
        Elf32_Phdr *p_phdr = (Elf32_Phdr *)(file_buffer + p_ehdr->e_phoff) + i;
        if (p_phdr -> p_type != PT_LOAD)
            continue;

        uint8_t *src = file_buffer + p_phdr->p_offset;
        uint8_t *dest = (uint8_t *)p_phdr->p_paddr;    // 分页尚未开启，使用物理地址
        for(int i = 0; i < p_phdr->p_filesz; i++)
            *dest++ = *src++;

        // 如果p_memsz大于p_filesz，则需要用0填充.bss段
        for (int i = 0; i < p_phdr->p_memsz - p_phdr->p_filesz; i++)
            *dest++ = 0;
    }

    return p_ehdr->e_entry;
}

/**
 * 死机
 */
static void die (int code) {
    for (;;) {}
}

#define CR4_PSE (1 << 4)
#define CR0_PG  (1 << 31)
#define PDE_P   (1 << 0)    //页目录项存在位
#define PDE_W   (1 << 1)    //页目录项RW位
#define PDE_PS  (1 << 7)    //4MB页的页目录项PS位

void enable_page_mode()
{
    // 页目录表，4KB对齐，低12位为0
    static uint32_t PDT[1024] __attribute__((aligned(0x1000))) = {
        [0] = PDE_P | PDE_W | PDE_PS
    };

    // CR4的PSE位置1，开启4MB大页
    write_cr4(read_cr4() | CR4_PSE);
    // 将页目录表地址写入CR3，PCD=PWT=0
    write_cr3((uint32_t)PDT);
    write_cr0(read_cr0() | CR0_PG);
}

void load_kernel(void)
{
    read_disk(100, 500, (uint16_t *)SYS_KERNEL_LOAD_ADDR);
    uint32_t kernel_entry = reload_elf_file((uint8_t *)SYS_KERNEL_LOAD_ADDR);
    if (kernel_entry == 0)
        die(-1);

    enable_page_mode();
    
    ((void (*) (boot_info_t *))kernel_entry)(&boot_info);
    for(;;);
}